/*-------------------------------------------------------------------------
 *
 * timeout.c
 *	  处理多个超时原因的SIGALRM中断的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, 加利福尼亚大学董事会
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/misc/timeout.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/time.h>

#include "miscadmin.h"
#include "storage/proc.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"


/* 关于任何一个超时原因的数据 */
typedef struct timeout_params
{
	TimeoutId	index;			/* 超时原因的标识符 */

	/* 可变的，因为这些可能会被信号处理程序改变 */
	volatile bool active;		/* 如果超时在 active_timeouts[] 中则为真 */
	volatile bool indicator;	/* 如果超时已经发生则为真 */

	/* 超时的回调函数，如果超时未注册则为 NULL */
	timeout_handler_proc timeout_handler;

	TimestampTz start_time;		/* 超时最后一次激活的时间 */
	TimestampTz fin_time;		/* 当前时间或最后一次到期的时间 */
	int			interval_in_ms; /* 触发之间的时间，或如果只触发一次则为 0 */
} timeout_params;

/*
 * 按 enum TimeoutId 的顺序列出可能的超时原因。
 */
static timeout_params all_timeouts[MAX_TIMEOUTS];
static bool all_timeouts_initialized = false;

/*
 * 按其 fin_time 和优先级排序的活动超时列表。
 * 此列表可能会被中断处理程序更改，因此它是可变的。
 */
static volatile int num_active_timeouts = 0;
static timeout_params *volatile active_timeouts[MAX_TIMEOUTS];

/*
 * 控制信号处理程序是否被允许执行任何操作的标志。
 * 这对于避免与处理程序的竞争条件非常有用。请注意，
 * 特别是这让我们能够在不费力地禁用和重新启用定时器信号的情况下，
 * 对数据结构进行更改。大多数情况下，在这样的关键区域内
 * 也不会发生中断，但如果发生，这条规则确保它是安全的。
 * 在多个操作之间保持信号启用也可以大大减少我们进行的
 * 内核调用次数。关于这一点，请参阅 schedule_alarm() 中的注释。
 *
 * 当我们不期望中断时，我们将其保持为“false”，以防万一。
 */
static volatile sig_atomic_t alarm_enabled = false;

#define disable_alarm() (alarm_enabled = false)
#define enable_alarm()	(alarm_enabled = true)

/*
 * 记录我们下次期望中断触发的状态。
 * (signal_due_at 仅在 signal_pending 为真时有效。)
 * 注意，信号处理程序会无条件地将 signal_pending 重置为
 * false，因此即使 alarm_enabled 为 false，它也可能异步更改。
 */
static volatile sig_atomic_t signal_pending = false;
static volatile TimestampTz signal_due_at = 0;


/*****************************************************************************
 * 内部辅助函数
 *
 * 对于所有这些，调用者有责任防止它们被信号处理程序中断。
 * 通常，首先调用 disable_alarm() 来防止中断，然后更新状态，
 * 最后调用 schedule_alarm()，在需要时重新启用信号处理程序。
 *****************************************************************************/

/*
 * 在活动数组中查找给定超时原因的索引。
 * 如果没有找到，则返回 -1。
 */
static int fc_find_active_timeout(TimeoutId fc_id)
{
	int			fc_i;

	for (fc_i = 0; fc_i < num_active_timeouts; fc_i++)
	{
		if (active_timeouts[fc_i]->index == fc_id)
			return fc_i;
	}

	return -1;
}

/*
 * 在给定索引处将指定的超时原因插入活动超时列表中。
 */
static void fc_insert_timeout(TimeoutId fc_id, int fc_index)
{
	int			fc_i;

	if (fc_index < 0 || fc_index > num_active_timeouts)
		elog(FATAL, "timeout index %d out of range 0..%d", fc_index,
			 num_active_timeouts);

	Assert(!all_timeouts[fc_id].active);
	all_timeouts[fc_id].active = true;

	for (fc_i = num_active_timeouts - 1; fc_i >= fc_index; fc_i--)
		active_timeouts[fc_i + 1] = active_timeouts[fc_i];

	active_timeouts[fc_index] = &all_timeouts[fc_id];

	num_active_timeouts++;
}

/*
 * 从超时列表中移除索引'th 元素。
 */
static void fc_remove_timeout_index(int fc_index)
{
	int			fc_i;

	if (fc_index < 0 || fc_index >= num_active_timeouts)
		elog(FATAL, "timeout index %d out of range 0..%d", fc_index,
			 num_active_timeouts - 1);

	Assert(active_timeouts[fc_index]->active);
	active_timeouts[fc_index]->active = false;

	for (fc_i = fc_index + 1; fc_i < num_active_timeouts; fc_i++)
		active_timeouts[fc_i - 1] = active_timeouts[fc_i];

	num_active_timeouts--;
}

/*
 * 启用指定的超时原因
 */
static void fc_enable_timeout(TimeoutId fc_id, TimestampTz fc_now, TimestampTz fc_fin_time,
			   int fc_interval_in_ms)
{
	int			fc_i;

	/* 断言请求是合理的 */
	Assert(all_timeouts_initialized);
	Assert(all_timeouts[fc_id].timeout_handler != NULL);

	/*
	 * 如果该超时已经处于活动状态，则暂时禁用它。
	 * 我们将这个调用解释为重新调度超时的指令。
	 */
	if (all_timeouts[fc_id].active)
		fc_remove_timeout_index(fc_find_active_timeout(fc_id));

	/*
	 * 找出插入新超时的索引位置。我们按 fin_time 排序，
	 * 对于相等的 fin_time 按优先级排序。
	 */
	for (fc_i = 0; fc_i < num_active_timeouts; fc_i++)
	{
		timeout_params *fc_old_timeout = active_timeouts[fc_i];

		if (fc_fin_time < fc_old_timeout->fin_time)
			break;
		if (fc_fin_time == fc_old_timeout->fin_time && fc_id < fc_old_timeout->index)
			break;
	}

	/*
	 * 将超时标记为活动，并将其插入活动列表中。
	 */
	all_timeouts[fc_id].indicator = false;
	all_timeouts[fc_id].start_time = fc_now;
	all_timeouts[fc_id].fin_time = fc_fin_time;
	all_timeouts[fc_id].interval_in_ms = fc_interval_in_ms;

	fc_insert_timeout(fc_id, fc_i);
}

/*
 * 为下一个活动超时时间调度警报，如果有的话
 *
 * 我们假设调用者已经获得了当前时间，或足够接近的
 * 近似值。（如果“现在”过去了一两次滴答声，或者在我们到达内核调用之前又过了一点时间，这是可以的；这会
 * 导致我们请求中断的时间比最近的
 * 超时晚一两次，但这没什么大不了的。不过，传递一个处于未来的“现在”值就不好了。）
 */
static void fc_schedule_alarm(TimestampTz fc_now)
{
	if (num_active_timeouts > 0)
	{
		struct itimerval fc_timeval;
		TimestampTz fc_nearest_timeout;
		long		fc_secs;
		int			fc_usecs;

		MemSet(&fc_timeval, 0, sizeof(struct itimerval));

		/*
		 * 如果我们认为有信号待处理，但当前时间已经超过
		 * 信号到期时的10毫秒，则假设超时
		 * 请求以某种方式丢失；清除signal_pending，以便我们将在下面重置
		 * 中断请求。（10毫秒对应于现代系统中最坏情况下的
		 * 超时粒度。）如果中断确实在我们到达
		 * setitimer()调用之间触发，也不会对我们造成伤害。
		 */
		if (signal_pending && fc_now > signal_due_at + 10 * 1000)
			signal_pending = false;

		/*
		 * 获取最近待处理超时的剩余时间。如果它是
		 * 负值，假设我们以某种方式错过了一个中断，并清除
		 * signal_pending。这给我们提供了另一个机会来恢复，如果内核因某种原因丢弃了超时请求。
		 */
		fc_nearest_timeout = active_timeouts[0]->fin_time;
		if (fc_now > fc_nearest_timeout)
		{
			signal_pending = false;
			/* 尽快强制触发中断 */
			fc_secs = 0;
			fc_usecs = 1;
		}
		else
		{
			TimestampDifference(fc_now, fc_nearest_timeout,
								&fc_secs, &fc_usecs);

			/*
			 * 差异可能小于微秒；
			 * 确保我们不是取消，而是设置中断。
			 */
			if (fc_secs == 0 && fc_usecs == 0)
				fc_usecs = 1;
		}

		fc_timeval.it_value.tv_sec = fc_secs;
		fc_timeval.it_value.tv_usec = fc_usecs;

		/*
		 * 我们必须在调用setitimer()之前启用信号处理程序；如果我们
		 * 以相反的顺序进行操作，就会出现竞争条件，其中
		 * 中断可能在我们能够设置alarm_enabled之前发生，这样信号处理程序就无法执行任何操作。
		 *
		 * 因为我们在disable_alarm()中没有关闭定时器，
		 * 所以在enable_alarm()和setitimer()之间，之前设定的中断可能会触发。这个是安全的。然而。
		 * 可能会有两种结果：
		 *
		 * 1. 信号处理程序发现没有需要处理的事情（因为最近的
		 * 超时事件仍然在未来）。它将重新设置定时器
		 * 并返回。然后我们会用一个新的值覆盖定时器值。
		 * 这将意味着定时器触发的时间比我们
		 * 意图的稍晚，但仅仅是信号处理程序做无用事情所需的时间，这应该不会太长。
		 *
		 * 2. 信号处理程序执行并删除一个或多个超时
		 * 事件。当它返回时，队列要么现在为空，要么
		 * 最前面的事件比我们上面查看的事件要晚。所以我们将
		 * 用一个过早的值（加上或减去
		 * 信号处理程序的执行时间）覆盖定时器值，从而导致无用的中断
		 * 发生。但是处理程序会重新设置定时器，
		 * 一切仍将按预期工作。
		 *
		 * 由于这些情况的概率非常低（这里的窗口
		 * 相当窄），因此在主线代码中增加周期以防止偶尔的冗余中断是没有价值的。
		 */
		enable_alarm();

		/*
		 * 如果已经有一个在所需时间之前或等于所需时间的中断挂起，我们无需再做任何事情。信号处理程序会在第一种情况下做出正确的处理，在第二种情况下重新调度中断。额外的中断似乎是多余的工作，但其实工作量并不大，而且这种方法在常见的使用场景中具有非常重要的优势，我们在这种情况下会反复设置一个我们不期望达到的超时，然后取消它。我们不必每次设置或取消超时时都调用setitimer()，而是以大约等于超时延迟的间隔执行一个中断和重新调度的setitimer()调用。例如，设置statement_timeout = 1s并且每秒处理数千个查询时，这种方法大约每秒需要一次中断和setitimer()调用，而不是每秒数千次的setitimer()调用。
		 *
		 * 由于我们获取“现在”和到达setitimer()之间可能经过了一段时间，内核对触发中断的时机的看法可能会比signal_due_at稍晚。这是可以接受的，原因如上所述。
		 */
		if (signal_pending && fc_nearest_timeout >= signal_due_at)
			return;

		/*
		 * 与调用enable_alarm()一样，我们必须在调用setitimer()之前设置signal_pending；如果我们在之后设置，信号处理程序可能会在我们设置之前触发，使我们错误地认为信号仍在到来。
		 *
		 * 与设置/检查signal_pending相关的其他竞争条件是可以的，原因如上所述。另一个额外的点是，信号处理程序可能会在我们设置signal_due_at之后，但在setitimer()调用之前触发。然后，处理程序可能会用它计算的值覆盖signal_due_at，那个值将与我们刚刚计算的值相同或可能更晚。在我们执行setitimer()之后，净效果是signal_due_at所给出的时间会晚于实际发生中断的时间；这是一个安全的情况。
		 */
		signal_due_at = fc_nearest_timeout;
		signal_pending = true;

		/* 设置警报计时器 */
		if (setitimer(ITIMER_REAL, &fc_timeval, NULL) != 0)
		{
			/*
			 * 在这里清除signal_pending有点形同虚设，但并非完全如此，因为FATAL退出路径中的某些东西可能会尝试使用超时功能。
			 */
			signal_pending = false;
			elog(FATAL, "could not enable SIGALRM timer: %m");
		}
	}
}


/*****************************************************************************
 * 信号处理程序
 *****************************************************************************/

/*
 * SIGALRM的信号处理程序
 *
 * 处理任何活动的超时原因，然后根据需要重新调度中断。
 */
static void fc_handle_sig_alarm(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	/*
	 * 增加延迟计数器，以确保我们调用的任何内容都不会直接处理中断。没有超时处理程序应该这样做，但这些故障很难调试，所以最好确保一下。
	 */
	HOLD_INTERRUPTS();

	/*
	 * SIGALRM始终是唤醒任何等待进程锁的原因。
	 */
	SetLatch(MyLatch);

	/*
	 * 始终重置signal_pending，即使!alarm_enabled，因为现在确实没有
	 * 信号待处理。
	 */
	signal_pending = false;

	/*
	 * 触发任何待处理的超时，但前提是我们被允许这样做。
	 */
	if (alarm_enabled)
	{
		/*
		 * 禁用警报，以防此平台允许信号处理程序
		 * 自我中断。 schedule_alarm()会在适当的情况下重新启用。
		 */
		disable_alarm();

		if (num_active_timeouts > 0)
		{
			TimestampTz fc_now = GetCurrentTimestamp();

			/* 当第一个待处理的超时被达到... */
			while (num_active_timeouts > 0 &&
				   fc_now >= active_timeouts[0]->fin_time)
			{
				timeout_params *fc_this_timeout = active_timeouts[0];

				/* 从活动列表中移除它 */
				fc_remove_timeout_index(0);

				/* 标记为已触发 */
				fc_this_timeout->indicator = true;

				/* 并调用其处理函数 */
				fc_this_timeout->timeout_handler();

				/* 如果它应该重复触发，重新启用它。 */
				if (fc_this_timeout->interval_in_ms > 0)
				{
					TimestampTz fc_new_fin_time;

					/*
					 * 为了防止漂移，根据预定的触发时间安排
					 * 超时的下一实例，而不是实际的触发时间。
					 * 但如果超时已经延迟到错过了整个周期，
					 * 则退回到根据实际触发时间进行调度。
					 */
					fc_new_fin_time =
						TimestampTzPlusMilliseconds(fc_this_timeout->fin_time,
													fc_this_timeout->interval_in_ms);
					if (fc_new_fin_time < fc_now)
						fc_new_fin_time =
							TimestampTzPlusMilliseconds(fc_now,
														fc_this_timeout->interval_in_ms);
					fc_enable_timeout(fc_this_timeout->index, fc_now, fc_new_fin_time,
								   fc_this_timeout->interval_in_ms);
				}

				/*
				 * 处理程序可能不会花费微不足道的时间（例如CheckDeadLock
				 * 并不便宜），所以让我们在每次之后更新我们对
				 * “现在”的理解。
				 */
				fc_now = GetCurrentTimestamp();
			}

			/* 完成超时触发，因此重新调度下一个中断（如果有的话） */
			fc_schedule_alarm(fc_now);
		}
	}

	RESUME_INTERRUPTS();

	errno = fc_save_errno;
}


/*****************************************************************************
 * 公共API
 *****************************************************************************/

/*
 * 初始化超时模块。
 *
 * 必须在每个想要使用超时的进程中调用此函数。
 *
 * 如果该进程是从另一个使用此模块的进程中派生的，
 * 请确保在重新启用信号之前调用此函数；否则原本在父进程中
 * 运行的处理程序可能会在此进程中被调用。
 */
void InitializeTimeouts(void)
{
	int			fc_i;

	/* 初始化或重新初始化所有局部状态 */
	disable_alarm();

	num_active_timeouts = 0;

	for (fc_i = 0; fc_i < MAX_TIMEOUTS; fc_i++)
	{
		all_timeouts[fc_i].index = fc_i;
		all_timeouts[fc_i].active = false;
		all_timeouts[fc_i].indicator = false;
		all_timeouts[fc_i].timeout_handler = NULL;
		all_timeouts[fc_i].start_time = 0;
		all_timeouts[fc_i].fin_time = 0;
		all_timeouts[fc_i].interval_in_ms = 0;
	}

	all_timeouts_initialized = true;

	/* 现在建立信号处理程序 */
	pqsignal(SIGALRM, fc_handle_sig_alarm);
}

/*
 * 注册超时原因
 *
 * 对于预定义的超时，这仅注册回调函数。
 *
 * 对于用户定义的超时，传递id == USER_TIMEOUT；我们将分配并
 * 返回一个超时ID。
 */
TimeoutId
RegisterTimeout(TimeoutId fc_id, timeout_handler_proc fc_handler)
{
	Assert(all_timeouts_initialized);

	/* 此处无需禁用信号处理程序。 */

	if (fc_id >= USER_TIMEOUT)
	{
		/* 分配用户定义的超时原因 */
		for (fc_id = USER_TIMEOUT; fc_id < MAX_TIMEOUTS; fc_id++)
			if (all_timeouts[fc_id].timeout_handler == NULL)
				break;
		if (fc_id >= MAX_TIMEOUTS)
			ereport(FATAL,
					(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
					 errmsg("cannot add more timeout reasons")));
	}

	Assert(all_timeouts[fc_id].timeout_handler == NULL);

	all_timeouts[fc_id].timeout_handler = fc_handler;

	return fc_id;
}

/*
 * 重新调度任何待处理的SIGALRM中断。
 *
 * 这可以在错误恢复期间使用，以防查询取消导致
 * 失去了SIGALRM事件（由于在handle_sig_alarm中跳出而无法
 * 做任何事情）。但请注意，如果在相同区域调用了任何公共的
 * enable_或disable_timeout函数，则不需要这样做，
 * 因为那些都在内部根据需要进行schedule_alarm()。
 */
void reschedule_timeouts(void)
{
	/* 为了灵活性，允许在我们初始化之前调用此函数。 */
	if (!all_timeouts_initialized)
		return;

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 重新调度中断，如果还有任何超时处于活动状态。 */
	if (num_active_timeouts > 0)
		fc_schedule_alarm(GetCurrentTimestamp());
}

/*
 * 允许指定的超时在指定延迟后触发。
 *
 * 延迟以毫秒为单位给出。
 */
void enable_timeout_after(TimeoutId fc_id, int fc_delay_ms)
{
	TimestampTz fc_now;
	TimestampTz fc_fin_time;

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 在适当的时间排队超时。 */
	fc_now = GetCurrentTimestamp();
	fc_fin_time = TimestampTzPlusMilliseconds(fc_now, fc_delay_ms);
	fc_enable_timeout(fc_id, fc_now, fc_fin_time, 0);

	/* 设置定时器中断。 */
	fc_schedule_alarm(fc_now);
}

/*
 * 启用指定的超时，使其周期性触发，触发之间的延迟为指定的时间。
 *
 * 延迟以毫秒为单位。
 */
void enable_timeout_every(TimeoutId fc_id, TimestampTz fc_fin_time, int fc_delay_ms)
{
	TimestampTz fc_now;

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 在适当的时间排队超时。 */
	fc_now = GetCurrentTimestamp();
	fc_enable_timeout(fc_id, fc_now, fc_fin_time, fc_delay_ms);

	/* 设置定时器中断。 */
	fc_schedule_alarm(fc_now);
}

/*
 * 在指定的时间启用指定的超时。
 *
 * 这用于支持由于某种原因需要根据“现在”以外的某个点计算超时的情况。
 * 如果没有这样的原因，请使用 enable_timeout_after()，以避免两次调用 GetCurrentTimestamp()。
 */
void enable_timeout_at(TimeoutId fc_id, TimestampTz fc_fin_time)
{
	TimestampTz fc_now;

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 在适当的时间排队超时。 */
	fc_now = GetCurrentTimestamp();
	fc_enable_timeout(fc_id, fc_now, fc_fin_time, 0);

	/* 设置定时器中断。 */
	fc_schedule_alarm(fc_now);
}

/*
 * 一次启用多个超时。
 *
 * 这就像多次调用 enable_timeout_after() 和/或 enable_timeout_at()。
 * 使用此方法减少建立多个超时所需的 GetCurrentTimestamp() 和 setitimer() 调用的数量。
 */
void enable_timeouts(const EnableTimeoutParams *fc_timeouts, int fc_count)
{
	TimestampTz fc_now;
	int			fc_i;

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 在适当的时候排队超时。 */
	fc_now = GetCurrentTimestamp();

	for (fc_i = 0; fc_i < fc_count; fc_i++)
	{
		TimeoutId	fc_id = fc_timeouts[fc_i].id;
		TimestampTz fc_fin_time;

		switch (fc_timeouts[fc_i].type)
		{
			case TMPARAM_AFTER:
				fc_fin_time = TimestampTzPlusMilliseconds(fc_now,
													   fc_timeouts[fc_i].delay_ms);
				fc_enable_timeout(fc_id, fc_now, fc_fin_time, 0);
				break;

			case TMPARAM_AT:
				fc_enable_timeout(fc_id, fc_now, fc_timeouts[fc_i].fin_time, 0);
				break;

			case TMPARAM_EVERY:
				fc_fin_time = TimestampTzPlusMilliseconds(fc_now,
													   fc_timeouts[fc_i].delay_ms);
				fc_enable_timeout(fc_id, fc_now, fc_fin_time, fc_timeouts[fc_i].delay_ms);
				break;

			default:
				elog(ERROR, "unrecognized timeout type %d",
					 (int) fc_timeouts[fc_i].type);
				break;
		}
	}

	/* 设置定时器中断。 */
	fc_schedule_alarm(fc_now);
}

/*
 * 取消指定的超时。
 *
 * 超时的已触发指示器被重置，除非 keep_indicator 为 true。
 *
 * 当一个超时被取消时，任何其他活动超时仍然有效。
 * 禁用一个未启用的超时不是错误。
 */
void disable_timeout(TimeoutId fc_id, bool fc_keep_indicator)
{
	/* 断言请求是合理的 */
	Assert(all_timeouts_initialized);
	Assert(all_timeouts[fc_id].timeout_handler != NULL);

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 找到超时并将其从活动列表中移除。 */
	if (all_timeouts[fc_id].active)
		fc_remove_timeout_index(fc_find_active_timeout(fc_id));

	/* 将其标记为非活动，无论它之前是否活动。 */
	if (!fc_keep_indicator)
		all_timeouts[fc_id].indicator = false;

	/* 重新调度中断，如果还有任何超时处于活动状态。 */
	if (num_active_timeouts > 0)
		fc_schedule_alarm(GetCurrentTimestamp());
}

/*
 * 一次取消多个超时。
 *
 * 超时的已触发指示器被重置，除非 timeouts[i].keep_indicator 为 true。
 *
 * 这就像多次调用 disable_timeout()。
 * 使用此方法减少取消多个超时所需的 GetCurrentTimestamp()
 * 和 setitimer() 调用的数量。
 */
void disable_timeouts(const DisableTimeoutParams *fc_timeouts, int fc_count)
{
	int			fc_i;

	Assert(all_timeouts_initialized);

	/* 为安全起见，禁用超时中断。 */
	disable_alarm();

	/* 取消超时。 */
	for (fc_i = 0; fc_i < fc_count; fc_i++)
	{
		TimeoutId	fc_id = fc_timeouts[fc_i].id;

		Assert(all_timeouts[fc_id].timeout_handler != NULL);

		if (all_timeouts[fc_id].active)
			fc_remove_timeout_index(fc_find_active_timeout(fc_id));

		if (!fc_timeouts[fc_i].keep_indicator)
			all_timeouts[fc_id].indicator = false;
	}

	/* 重新调度中断，如果还有任何超时处于活动状态。 */
	if (num_active_timeouts > 0)
		fc_schedule_alarm(GetCurrentTimestamp());
}

/*
 * 禁用信号处理程序，从活动列表中移除所有超时，
 * 可选择性地重置它们的超时指示器。
 */
void disable_all_timeouts(bool fc_keep_indicators)
{
	int			fc_i;

	disable_alarm();

	/*
	 * 我们以前在这里禁用计时器中断，但在常见的使用模式中，
	 * 保持其启用成本更低；这可能会让我们避免在短时间内再次启用它。
	 * 请参见 schedule_alarm() 中的评论。
	 */

	num_active_timeouts = 0;

	for (fc_i = 0; fc_i < MAX_TIMEOUTS; fc_i++)
	{
		all_timeouts[fc_i].active = false;
		if (!fc_keep_indicators)
			all_timeouts[fc_i].indicator = false;
	}
}

/*
 * 如果超时处于活动状态（已启用且尚未触发），则返回 true
 *
 * 当然，这受到竞争条件的影响，因为超时可能会在我们查看后立即触发。
 */
bool get_timeout_active(TimeoutId fc_id)
{
	return all_timeouts[fc_id].active;
}

/*
 * 返回超时的已触发指示器
 *
 * 如果 reset_indicator 为 true，则在返回 true 时重置指示器。
 * 为了避免由于竞争条件而遗漏超时，我们会小心在返回 false 时不重置指示器。
 */
bool get_timeout_indicator(TimeoutId fc_id, bool fc_reset_indicator)
{
	if (all_timeouts[fc_id].indicator)
	{
		if (fc_reset_indicator)
			all_timeouts[fc_id].indicator = false;
		return true;
	}
	return false;
}

/*
 * 返回最近一次激活超时的时间
 *
 * 注意：如果在此进程中超时从未被激活，则返回0。
 * 然而，当超时发生时，我们并不重置start_time，以避免在某段代码
 * 正要获取该值时，SIGALRM刚好触发而造成竞态条件。
 */
TimestampTz get_timeout_start_time(TimeoutId fc_id)
{
	return all_timeouts[fc_id].start_time;
}

/*
 * 返回超时到期的时间，或者最近一次到期的时间
 *
 * 注意：如果在此进程中超时从未被激活，则返回0。
 * 然而，当超时发生时，我们并不重置fin_time，以避免在某段代码
 * 正要获取该值时，SIGALRM刚好触发而造成竞态条件。
 */
TimestampTz get_timeout_finish_time(TimeoutId fc_id)
{
	return all_timeouts[fc_id].fin_time;
}
